package cn.bearspark.sms.provider.service.impl;

import cn.bearspark.common.dto.ResponseDTO;
import cn.bearspark.sms.enumeration.SmsFailRespEnum;
import cn.bearspark.sms.provider.config.SmsProperties;
import cn.bearspark.sms.provider.dao.mapper.SmsMapper;
import cn.bearspark.sms.provider.dao.po.SmsPO;
import cn.bearspark.sms.provider.key.SmsCacheKeyBuilder;
import cn.bearspark.sms.provider.service.SmsService;
import com.cloopen.rest.sdk.BodyType;
import com.cloopen.rest.sdk.CCPRestSmsSDK;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author f
 */
@Service
public class SmsServiceImpl implements SmsService {

    private static final Logger LOGGER = LoggerFactory.getLogger(SmsServiceImpl.class);

    /**
     * 异步发送短信的线程池，最少有 2 个线程，最多有 8 个线程，每个线程活跃的时间是 3 秒，有一个容纳 1000 个任务的队列
     */
    private static final ThreadPoolExecutor SEND_SMS_POOL = new ThreadPoolExecutor(2, 8, 3, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(1000),
            r -> new Thread(r, "sendMsgThread-" + ThreadLocalRandom.current().nextInt(10000))
    );

    /**
     * 设置验证码在 redis 中存储的过期时间（单位：ms），默认为 3min = 填写验证码 1min + 填写密码 2min
     */
    private static final int CODE_CACHE_EXPIRE = 1000 * 60 * 3;

    private final SmsMapper smsMapper;

    private final RedisTemplate<String, Object> redisTemplate;
    private final SmsCacheKeyBuilder smsCacheKeyBuilder;

    private final SmsProperties smsProperties;

    public SmsServiceImpl(SmsMapper smsMapper,
                          RedisTemplate<String, Object> redisTemplate,
                          SmsCacheKeyBuilder smsCacheKeyBuilder,
                          SmsProperties smsProperties) {
        this.smsMapper = smsMapper;
        this.redisTemplate = redisTemplate;
        this.smsCacheKeyBuilder = smsCacheKeyBuilder;
        this.smsProperties = smsProperties;
    }

    @Override
    public ResponseDTO<Object> send(String phone) {
        // 检查该用户是否在 3 分钟之内有验证码的记录，如果有，则不需要发送验证码
        String cacheKey = smsCacheKeyBuilder.buildLoginCodeKey(phone);
        if (redisTemplate.opsForValue().get(cacheKey) != null) {
            return SmsFailRespEnum.FREQUENT_SMS.response();
        }

        // 生成验证码（长度为 4 位，有效期为 3 分钟）
        int code = new Random().nextInt(1000, 9999);
        redisTemplate.opsForValue().set(cacheKey, code, CODE_CACHE_EXPIRE, TimeUnit.MILLISECONDS);

        // 异步发送验证码
        SEND_SMS_POOL.execute(() -> {
            // TODO 完成整个项目后再解开这里的代码，从而发送真正的短信
//            if (!sendSmsWithCCP(phone, code)) {
            if (!mockSendSms(phone, code)) {
                LOGGER.warn("发送验证码失败, phone is {}", phone);
                return;
            }
            // 插入验证码发送记录
            smsMapper.insert(SmsPO.of(phone));
        });

        return ResponseDTO.success();
    }

    @Override
    public ResponseDTO<Object> verify(String phone, String code) {
        // 校验 redis 中存储的验证码和传入的验证码是否一致（而且 redis 中存储的验证码不能过期）
        String cacheKey = smsCacheKeyBuilder.buildLoginCodeKey(phone);
        Integer cacheCode = (Integer) redisTemplate.opsForValue().get(cacheKey);
        if (cacheCode == null || !cacheCode.equals(Integer.valueOf(code))) {
            return SmsFailRespEnum.CODE_EXPIRE_OR_INCORRECT.response();
        }

        return ResponseDTO.success();
    }

    // 模拟发送短信
    private boolean mockSendSms(String phone, int code) {
        try {
            LOGGER.info("发送短信, phone is {}, code is {}", phone, code);
            Thread.sleep(1000);
            LOGGER.info("短信发送成功");
            return true;
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    // 使用容联云向用户手机发送信息（目前只支持向指定手机号发送）
    private boolean sendSmsWithCCP(String phone, int code) {
        try {
            // 获取容联云的 sdk 对象
            CCPRestSmsSDK sdk = new CCPRestSmsSDK();
            sdk.init(smsProperties.getSmsServerIp(), String.valueOf(smsProperties.getPort()));
            sdk.setAccount(smsProperties.getAccountSId(), smsProperties.getAccountToken());
            sdk.setAppId(smsProperties.getAppId());
            sdk.setBodyType(BodyType.Type_JSON);

            // 获取发送的手机号
            String to = getTestPhone(smsProperties.getTestPhoneList());
            // 获取模版 id
            String templateId = String.valueOf(smsProperties.getTemplateId());
            // 【云通讯】您的验证码是{1}，请于{2}分钟内正确输入。其中{1}和{2}为短信模板参数。
            String[] sendData = {String.valueOf(code), "1"};
            // 可选 扩展码，四位数字 0~9999
            String subAppend = "1234";
            // 可选 第三方自定义消息id，最大支持32位英文数字，同账号下同一自然天内不允许重复
            String reqId = UUID.randomUUID().toString();

            // 发送短信
            Map<String, Object> result = sdk.sendTemplateSMS(to, templateId, sendData, subAppend, reqId);
            LOGGER.info("手机号: {}, 短信验证码: {}", phone, code);

            if (!("000000".equals(result.get("statusCode")))) {
                // 异常返回输出错误码和错误信息
                LOGGER.error("错误码: {}, 错误信息: {}", result.get("statusCode"), result.get("statusMsg"));
                return false;
            }

            // TODO 这里会发生什么事情？如果不需要，则移除这段代码
            // 正常返回输出data包体信息（map）
            Map<String, Object> data = (Map<String, Object>) result.get("data");
            Set<String> keySet = data.keySet();
            for (String key : keySet) {
                Object object = data.get(key);
                LOGGER.info("key is {}, object is {}", key, object);
            }
            return true;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 获取测试的手机号，目前采用随机获取的模式
    private String getTestPhone(List<String> testPhoneList) {
        return testPhoneList.get(new Random().nextInt(testPhoneList.size()));
    }

}
